package cn.kgc.vue.shiro;

import cn.hutool.crypto.digest.MD5;
import cn.kgc.vue.entity.RolePers;
import cn.kgc.vue.entity.UserRole;
import cn.kgc.vue.exceptions.TokenTheftException;
import cn.kgc.vue.mapper.PermissionMapper;
import cn.kgc.vue.mapper.RolePersMapper;
import cn.kgc.vue.mapper.UserMapper;
import cn.kgc.vue.mapper.UserRoleMapper;
import cn.kgc.vue.utils.IpUtil;
import cn.kgc.vue.utils.JWTUtil;
import com.auth0.jwt.exceptions.TokenExpiredException;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.StringUtils;
import lombok.extern.slf4j.Slf4j;
import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.AuthenticationInfo;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.authc.SimpleAuthenticationInfo;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.authz.SimpleAuthorizationInfo;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.subject.PrincipalCollection;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;

/**
 * @author 课工场
 * @date 2023/12/2
 * @description 用户自定义域对象  实现登录和权限的持久层操作
 */
@Slf4j
public class CustomerRealm extends AuthorizingRealm {

    @Resource
    private UserMapper userMapper;

    @Resource
    private StringRedisTemplate stringRedisTemplate;

    @Resource
    private JWTUtil jwtUtil;

    @Resource
    private UserRoleMapper userRoleMapper;

    @Resource
    private RolePersMapper rolePersMapper;

    @Resource
    private PermissionMapper permissionMapper;


    @Override
    public boolean  supports(AuthenticationToken token){
        return  token instanceof  JWTToken;
    }

    // 授权
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {

        Integer uid = (Integer) principals.getPrimaryPrincipal();  //  uid   new SimpleAuthenticationInfo(uid, tk, this.getName());

        // 1.根据用户id  查询角色id

        List<Integer> rids = userRoleMapper.selectList(new QueryWrapper<UserRole>().eq("user_id", uid))
                .stream().map(ur -> ur.getRoleId()).collect(Collectors.toList());
        //  2.根据角色id  查询权限id

        Set<Integer> pIds = rolePersMapper.selectList(new QueryWrapper<RolePers>().in("role_id", rids))
                .stream()
                .map(rp->rp.getPerId()).collect(Collectors.toSet());

        // 3. 根据权限id   查询权限字符串
        List<String> permissions = permissionMapper.selectBatchIds(pIds)
                .stream().map(permission -> permission.getPermission()).collect(Collectors.toList());

        SimpleAuthorizationInfo simpleAuthorizationInfo = new SimpleAuthorizationInfo();
        simpleAuthorizationInfo.addStringPermissions(permissions);
        return simpleAuthorizationInfo;
    }


    // 认证
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {

        //  token  ->  string  token
        JWTToken jwtToken = (JWTToken) token;
        String tk = (String) jwtToken.getPrincipal();

        // 1. 判定redis中是否包含当前token
        if (!stringRedisTemplate.hasKey(MD5.create().digestHex(tk))) {
           throw new TokenExpiredException("token被移除了");
        }

        // 2. token校验  是否被盗用
        String redisIp = stringRedisTemplate.opsForValue().get(MD5.create().digestHex(tk));
        ServletRequestAttributes requestAttributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        HttpServletRequest req = requestAttributes.getRequest();
        String ip = IpUtil.getIp(req);
        if (!StringUtils.equals(redisIp, ip)) {
             throw new TokenTheftException();
        }

        // token 真伪
        jwtUtil.verifyToken(tk);

        // token  解析
        Map<String, Object> claims = jwtUtil.getClaims(tk);
        Object uid = claims.get("uid");

        SimpleAuthenticationInfo simpleAuthenticationInfo = new SimpleAuthenticationInfo(uid, tk, this.getName());

        return simpleAuthenticationInfo;
    }
}
